package com.ruoyi.common.utils.ps2;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.*;

public class ImageCompareTool {

    // ===== 固定路径配置 =====
    private static final String BASE_DIR = "D:\\BaiduNetdiskDownload\\指南针-2025.5.28-122-第一批\\杜雯欣100-8.27";
    private static final String EXCEL_FILE = BASE_DIR + "\\metadata.xlsx";
    private static final String ORIGIN_IMAGE_DIR = BASE_DIR + "\\原图";
    private static final String EDITED_IMAGE_DIR = BASE_DIR + "\\处理图";
    private static final String BAD_IMAGE_DIR = BASE_DIR + "\\不合格"; // 输出不合格文件夹
    private static final String MASK_IMAGE_DIR = null; // 掩膜文件夹（可为 null）

    private static final Logger LOG = Logger.getLogger(ImageCompareTool.class.getName());
    private static final List<String> DEFAULT_EXTS = Arrays.asList("png", "jpg", "jpeg", "bmp", "tif", "tiff");

    public static void main(String[] args) {
        try {
            AppConfig cfg = new AppConfig();
            cfg.excelPath = EXCEL_FILE;
            cfg.origDir = ORIGIN_IMAGE_DIR;
            cfg.procDir = EDITED_IMAGE_DIR;
            cfg.badDir = BAD_IMAGE_DIR;
            cfg.maskDir = MASK_IMAGE_DIR;
            cfg.extensions = DEFAULT_EXTS;

            bootstrapLogger(cfg);
            LOG.info("开始读取 Excel 并执行图片一致性校验...");

            List<RowTask> tasks = ExcelReader.readTasks(cfg);
            LOG.info("Excel 行数(含表头)：" + tasks.size());

            Path badDir = Paths.get(cfg.badDir);
            Files.createDirectories(badDir);

            String ts = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            Path csvLog = Paths.get("compare_log_" + ts + ".csv");

            try (BufferedWriter writer = Files.newBufferedWriter(csvLog, StandardCharsets.UTF_8)) {
                writeCsvHeader(writer);

                int pass = 0, fail = 0;
                for (RowTask t : tasks) {
                    CompareResult r = compareOne(t, cfg);
                    if (r.pass) {
                        pass++;
                    } else {
                        fail++;
                        if (t.procPath != null && Files.exists(t.procPath)) {
                            Path target = badDir.resolve(t.procPath.getFileName());
                            Files.move(t.procPath, target, StandardCopyOption.REPLACE_EXISTING);
                            LOG.warning("已移动不合格处理图到: " + target.toAbsolutePath());
                        }
                    }
                    writeCsvLine(writer, r);
                }
                LOG.info(String.format("处理完成：合格=%d，不合格=%d，CSV日志=%s", pass, fail, csvLog.toAbsolutePath()));
            }
        } catch (Exception e) {
            LOG.log(Level.SEVERE, "程序异常", e);
        }
    }

    private static CompareResult compareOne(RowTask task, AppConfig cfg) {
        CompareResult result = new CompareResult();
        result.rowIndex = task.rowIndex;
        result.origBase = task.origBase;
        result.procBase = task.procBase;
        result.maskBase = task.maskBase;

        task.origPath = findByBasename(task.origBase, cfg.origDir, cfg.extensions);
        task.procPath = findByBasename(task.procBase, cfg.procDir, cfg.extensions);
        if (cfg.maskDir != null) {
            task.maskPath = findByBasename(task.maskBase, cfg.maskDir, cfg.extensions);
        } else {
            task.maskPath = firstExists(
                    findByBasename(task.maskBase, cfg.origDir, cfg.extensions),
                    findByBasename(task.maskBase, cfg.procDir, cfg.extensions)
            );
        }

        if (task.origPath == null || !Files.exists(task.origPath)) {
            result.pass = false;
            result.failReasons.add("原图不存在: " + safe(task.origPath));
            return result;
        }
        if (task.procPath == null || !Files.exists(task.procPath)) {
            result.pass = false;
            result.failReasons.add("处理图不存在: " + safe(task.procPath));
            return result;
        }

        try {
            BufferedImage orig = ImageIO.read(task.origPath.toFile());
            BufferedImage proc = ImageIO.read(task.procPath.toFile());
            BufferedImage mask = null;
            if (task.maskPath != null && Files.exists(task.maskPath)) {
                mask = ImageIO.read(task.maskPath.toFile());
            }

            result.origWidth = orig.getWidth();
            result.origHeight = orig.getHeight();
            result.procWidth = proc.getWidth();
            result.procHeight = proc.getHeight();
            result.origSize = Files.size(task.origPath);
            result.procSize = Files.size(task.procPath);

            if (orig.getWidth() != proc.getWidth() || orig.getHeight() != proc.getHeight()) {
                result.pass = false;
                result.failReasons.add(String.format("分辨率不一致 原图=%dx%d 处理图=%dx%d", orig.getWidth(), orig.getHeight(), proc.getWidth(), proc.getHeight()));
                return result;
            }

            PixelCompareStats stats = comparePixels(orig, proc, mask);
            result.diffPixels = stats.diffPixels;
            result.firstDiffX = stats.firstDiffX;
            result.firstDiffY = stats.firstDiffY;

            if (stats.diffPixels > 0) {
                result.pass = false;
                result.failReasons.add(String.format("像素不一致(掩膜外) diffPixels=%d firstDiff=(%d,%d)", stats.diffPixels, stats.firstDiffX, stats.firstDiffY));
            } else {
                result.pass = true;
            }
        } catch (Exception ex) {
            result.pass = false;
            result.failReasons.add("读取或比较图片失败: " + ex.getMessage());
        }
        return result;
    }

    private static PixelCompareStats comparePixels(BufferedImage a, BufferedImage b, BufferedImage mask) {
        final int w = a.getWidth();
        final int h = a.getHeight();
        PixelCompareStats stats = new PixelCompareStats();
        stats.firstDiffX = -1;
        stats.firstDiffY = -1;
        boolean hasMask = (mask != null && mask.getWidth() == w && mask.getHeight() == h);

        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                if (hasMask) {
                    int m = mask.getRGB(x, y);
                    int mr = (m >> 16) & 0xFF, mg = (m >> 8) & 0xFF, mb = m & 0xFF;
                    int gray = (mr + mg + mb) / 3;
                    if (gray >= 128) continue;
                }
                if (a.getRGB(x, y) != b.getRGB(x, y)) {
                    stats.diffPixels++;
                    if (stats.firstDiffX < 0) {
                        stats.firstDiffX = x;
                        stats.firstDiffY = y;
                    }
                }
            }
        }
        return stats;
    }

    private static void writeCsvHeader(BufferedWriter w) throws IOException {
        w.write("rowIndex,origBase,procBase,maskBase,pass,failReasons,origWxH,procWxH,origSizeBytes,procSizeBytes,diffPixels,firstDiffX,firstDiffY,origPath,procPath,maskPath\n");
    }

    private static void writeCsvLine(BufferedWriter w, CompareResult r) throws IOException {
        String wxh1 = (r.origWidth>0? r.origWidth + "x" + r.origHeight : "");
        String wxh2 = (r.procWidth>0? r.procWidth + "x" + r.procHeight : "");
        String reasons = String.join(" | ", r.failReasons);
        w.write(String.join(",",
                csv(r.rowIndex), csv(r.origBase), csv(r.procBase), csv(r.maskBase), csv(r.pass),
                csv(reasons), csv(wxh1), csv(wxh2), csv(r.origSize), csv(r.procSize),
                csv(r.diffPixels), csv(r.firstDiffX), csv(r.firstDiffY),
                csv(safe(r.origPath)), csv(safe(r.procPath)), csv(safe(r.maskPath))));
        w.write("\n");
    }

    private static String csv(Object o) {
        String s = String.valueOf(o==null?"":o);
        if (s.contains(",") || s.contains("\"") || s.contains("\n")) {
            s = '"' + s.replace("\"", "\"\"") + '"';
        }
        return s;
    }

    private static String safe(Path p) { return p==null?"":p.toAbsolutePath().toString(); }
    private static Path firstExists(Path... candidates) { for (Path p : candidates) { if (p != null && Files.exists(p)) return p; } return null; }
    private static Path findByBasename(String basename, String dir, List<String> exts) {
        if (basename == null || basename.trim().isEmpty() || dir == null) return null;
        Path baseDir = Paths.get(dir);
        for (String ext : exts) {
            Path p = baseDir.resolve(basename + "." + ext);
            if (Files.exists(p)) return p;
        }
        return null;
    }

    static class RowTask {
        int rowIndex; String origBase; String procBase; String maskBase;
        Path origPath; Path procPath; Path maskPath;
    }
    static class CompareResult {
        int rowIndex; String origBase; String procBase; String maskBase; boolean pass;
        List<String> failReasons = new ArrayList<>();
        int origWidth, origHeight, procWidth, procHeight; long origSize, procSize;
        long diffPixels; int firstDiffX, firstDiffY;
        Path origPath, procPath, maskPath;
    }
    static class PixelCompareStats { long diffPixels; int firstDiffX; int firstDiffY; }

    static class ExcelReader {
        static List<RowTask> readTasks(AppConfig cfg) throws IOException {
            List<RowTask> list = new ArrayList<>();
            try (InputStream in = Files.newInputStream(Paths.get(cfg.excelPath)); Workbook wb = new XSSFWorkbook(in)) {
                Sheet sheet = wb.getSheetAt(0);
                int rows = sheet.getPhysicalNumberOfRows();
                for (int r = 0; r < rows; r++) {
                    Row row = sheet.getRow(r);
                    if (row == null) continue;
                    if (r == 0) continue;
                    RowTask t = new RowTask();
                    t.rowIndex = r + 1;
                    t.origBase = getString(row, 0);
                    t.procBase = getString(row, 1);
                    t.maskBase = getString(row, 2);
                    if ((t.origBase==null||t.origBase.isEmpty()) && (t.procBase==null||t.procBase.isEmpty())) continue;
                    list.add(t);
                }
            }
            return list;
        }
        private static String getString(Row row, int idx) {
            Cell c = row.getCell(idx, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
            if (c == null) return null;
            c.setCellType(CellType.STRING);
            String s = c.getStringCellValue();
            return (s == null) ? null : s.trim();
        }
    }

    static class AppConfig { String excelPath; String origDir; String procDir; String maskDir; String badDir; List<String> extensions; }

    private static void bootstrapLogger(AppConfig cfg) throws IOException {
        LogManager.getLogManager().reset();
        Logger root = Logger.getLogger("");
        root.setLevel(Level.INFO);
        ConsoleHandler console = new ConsoleHandler();
        console.setLevel(Level.INFO);
        root.addHandler(console);
        String ts = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        FileHandler file = new FileHandler("run_" + ts + ".log");
        file.setLevel(Level.ALL);
        file.setEncoding("UTF-8");
        file.setFormatter(new SimpleFormatter());
        root.addHandler(file);
        LOG.info("日志初始化完成");
        LOG.info("参数: excel=" + cfg.excelPath + ", origDir=" + cfg.origDir + ", procDir=" + cfg.procDir + ", maskDir=" + cfg.maskDir + ", badDir=" + cfg.badDir);
    }
}
